package com.shareware.common.utils

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Xml
import androidx.core.app.ActivityCompat
import com.shareware.common.NORMAL_ZERO
import com.shareware.common.NUMBER_ONE
import com.shareware.common.getContextGlobal
import com.shareware.common.listener.Callback
import com.shareware.common.model.AddressModel
import com.shareware.common.model.GpsInfo
import com.shareware.common.model.LocationInfo
import io.reactivex.Observable
import io.reactivex.ObservableEmitter
import io.reactivex.schedulers.Schedulers
import org.xmlpull.v1.XmlPullParser
import java.util.*
import java.util.concurrent.CopyOnWriteArraySet

/**
 * desc: 功能描述
 * email: mobiledeveloper@qq.com
 */
class LocationExt {

    @SuppressLint("CheckResult")
    companion object {
        private const val PARSE_PROVINCE = "province"
        private const val PARSE_CITY = "city"
        private const val PARSE_AREA = "district"
        private const val LOCATION_INTERVAL = 2000L
        private const val LOCATION_DELAY = 5000L

        private var lastLocationInfo: LocationInfo? = null

        private var emitter: ObservableEmitter<Location>? = null
        private var locationManager: LocationManager? = null

        private val locationMonitor: LocationMonitor = LocationMonitor()

        private val locationOnce = CopyOnWriteArraySet<Callback<LocationInfo?>>()

        @SuppressLint("ConstantLocale")
        private val geoCoder = Geocoder(getContextGlobal(), Locale.getDefault())
        private val mCities = hashMapOf<String, HashMap<String, AddressModel>>()
        private var removeTime: Long = 0
        private var serviceRunning = false

        @SuppressLint("MissingPermission")
        private val handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(message: Message) {
                if (message.what == NORMAL_ZERO) {
                    if (System.currentTimeMillis() - removeTime < 2000) {
                        sendEmptyMessageDelayed(NORMAL_ZERO, 2000)
                        return
                    }
                    removeMessages(NUMBER_ONE)
                    when {
                        locationManager == null -> {
                            locationOnce.forEach { once ->
                                once.call(null)
                            }
                            locationOnce.clear()
                            locationManager?.removeUpdates(locationMonitor)
                            removeTime = System.currentTimeMillis()
                            serviceRunning = false
                        }
                        locationManager!!.isProviderEnabled(LocationManager.GPS_PROVIDER) -> {
                            if (!serviceRunning) {
                                tryMethodIgnore {
                                    locationManager!!.requestLocationUpdates(
                                        LocationManager.GPS_PROVIDER,
                                        LOCATION_INTERVAL,
                                        0f,
                                        locationMonitor,
                                        Looper.getMainLooper()
                                    )
                                }
                            }
                        }
                        locationManager!!.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> {
                            if (!serviceRunning) {
                                tryMethodIgnore {
                                    locationManager!!.requestLocationUpdates(
                                        LocationManager.NETWORK_PROVIDER,
                                        LOCATION_INTERVAL,
                                        0f,
                                        locationMonitor,
                                        Looper.getMainLooper()
                                    )
                                }
                            }
                        }
                        else -> {
                            locationOnce.forEach { once ->
                                once.call(null)
                            }
                            locationOnce.clear()
                            locationManager?.removeUpdates(locationMonitor)
                            removeTime = System.currentTimeMillis()
                            serviceRunning = false
                        }
                    }
                } else {
                    locationManager?.removeUpdates(locationMonitor)
                    removeTime = System.currentTimeMillis()
                    serviceRunning = false
                }
            }
        }

        init {
            Observable.create<Location> {
                tryMethod {
                    val locationStream = getContextGlobal().assets.open("location.xml")
                    val parser = Xml.newPullParser()
                    parser.setInput(locationStream, Xml.Encoding.UTF_8.name)
                    var eventType = parser.eventType
                    var addressModel: AddressModel? = null
                    var provinceName = ""
                    var provinceCode = ""
                    var cityName = ""
                    var mAddress: HashMap<String, AddressModel> = hashMapOf()
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        when (eventType) {
                            XmlPullParser.START_DOCUMENT -> {
                                mCities.clear()
                            }
                            XmlPullParser.START_TAG -> {
                                val name = parser.name
                                if (name == PARSE_PROVINCE) {
                                    provinceName = parser.getAttributeValue(0)
                                    provinceCode = parser.getAttributeValue(1)
                                } else if (name == PARSE_CITY) {
                                    cityName = parser.getAttributeValue(0)
                                    val areaCode = parser.getAttributeValue(1)
                                    val cityCode = parser.getAttributeValue(2)
                                    addressModel = AddressModel(
                                        provinceName, provinceCode,
                                        cityName, cityCode, cityName, areaCode
                                    )
                                    mAddress[cityName] = addressModel
                                } else if (name == PARSE_AREA) {
                                    if (addressModel != null) {
                                        val areaName = parser.getAttributeValue(0)
                                        val areaCode = parser.getAttributeValue(1)
                                        mAddress[areaName] = addressModel.copy(
                                            areaName = areaName,
                                            areaCode = areaCode
                                        )
                                    }
                                }
                            }
                            XmlPullParser.END_TAG -> {
                                if (PARSE_CITY == parser.name) {
                                    mCities[cityName] = mAddress
                                    mAddress = hashMapOf()
                                }
                            }
                        }
                        eventType = parser.next()
                    }
                }
                emitter = it
            }.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe { location ->
                val gpsInfo = GpsInfo(
                    location.longitude, location.latitude,
                    location.altitude, location.speed, location.bearing
                )
                val fromLocation = try {
                    geoCoder.getFromLocation(gpsInfo.latitude, gpsInfo.longitude, 1)
                } catch (ignore: Exception) {
                    null
                }
                lastLocationInfo = if (fromLocation.isNullOrEmpty()) {
                    LocationInfo(AddressModel(), gpsInfo, "", "", System.currentTimeMillis())
                } else {
                    fromLocation[0]?.let {
                        var provinceName = ""
                        var provinceCode = ""
                        var cityName = ""
                        var cityCode = ""
                        var areaName = ""
                        var areaCode = ""
                        if (!it.locality.isNullOrEmpty()) {
                            val cityMap = mCities[it.locality]
                            if (!cityMap.isNullOrEmpty()) {
                                if (!it.subLocality.isNullOrEmpty()) {
                                    val addressModel = cityMap[it.subLocality]
                                    if (addressModel == null) {
                                        for ((name, address) in cityMap) {
                                            if (name.contains(it.subLocality)) {
                                                provinceName = address.provinceName
                                                provinceCode = address.provinceCode
                                                cityName = address.cityName
                                                cityCode = address.cityCode
                                                areaName = address.areaName
                                                areaCode = address.areaCode
                                                break
                                            }
                                        }
                                    } else {
                                        provinceName = addressModel.provinceName
                                        provinceCode = addressModel.provinceCode
                                        cityName = addressModel.cityName
                                        cityCode = addressModel.cityCode
                                        areaName = addressModel.areaName
                                        areaCode = addressModel.areaCode
                                    }
                                }
                                if (cityCode.isEmpty() || areaCode.isEmpty()) {
                                    cityMap[it.locality]?.let { address ->
                                        provinceName = address.provinceName
                                        provinceCode = address.provinceCode
                                        cityName = address.cityName
                                        cityCode = address.cityCode
                                        areaName = address.areaName
                                        areaCode = address.areaCode
                                    }
                                }
                            }
                        }
                        if (provinceName.isEmpty()) {
                            provinceName = it.adminArea ?: ""
                        }
                        if (cityName.isEmpty()) {
                            cityName = it.locality ?: ""
                        }
                        if (areaName.isEmpty()) {
                            areaName = it.subLocality ?: ""
                        }
                        LocationInfo(
                            AddressModel(
                                provinceName,
                                provinceCode,
                                cityName,
                                cityCode,
                                areaName,
                                areaCode
                            ),
                            gpsInfo,
                            it.getAddressLine(0) ?: "",
                            it.countryName ?: "",
                            System.currentTimeMillis()
                        )
                    }
                }
                locationOnce.forEach {
                    it.call(lastLocationInfo)
                }
                locationOnce.clear()
                handler.sendEmptyMessageDelayed(NUMBER_ONE, LOCATION_DELAY)
            }
        }

        /**
         * 获取当前定位
         */
        fun getLocation(): GpsInfo? {
            var lastLocation: GpsInfo? = null
            val locationManager =
                getContextGlobal().getSystemService(Context.LOCATION_SERVICE) as? LocationManager
                    ?: return null
            if (ActivityCompat.checkSelfPermission(
                    getContextGlobal(),
                    Manifest.permission.ACCESS_FINE_LOCATION
                )
                == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(
                    getContextGlobal(),
                    Manifest.permission.ACCESS_COARSE_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                    val location =
                        locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
                    if (location != null) {
                        lastLocation = GpsInfo(
                            location.longitude,
                            location.latitude,
                            location.altitude,
                            location.speed,
                            location.bearing
                        )
                    }
                }
                if (lastLocation == null && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                    val location =
                        locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
                    if (location != null) {
                        lastLocation = GpsInfo(
                            location.longitude,
                            location.latitude,
                            location.altitude,
                            location.speed,
                            location.bearing
                        )
                    }
                }
            }
            return lastLocation
        }

        /**
         * 异步获取定位
         */
        fun getLocationOnce(callback: Callback<LocationInfo?>?) {
            if (ActivityCompat.checkSelfPermission(
                    getContextGlobal(),
                    Manifest.permission.ACCESS_FINE_LOCATION
                )
                == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(
                    getContextGlobal(),
                    Manifest.permission.ACCESS_COARSE_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                if (locationManager == null) {
                    locationManager =
                        getContextGlobal().getSystemService(Context.LOCATION_SERVICE) as? LocationManager
                }
                if (locationManager == null) {
                    callback?.call(null)
                    return
                }
                if (locationManager!!.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
                    locationManager!!.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
                ) {
                    if (callback != null) {
                        if (!locationOnce.contains(callback)) {
                            locationOnce.add(callback)
                        }
                    }
                    if (!handler.hasMessages(NORMAL_ZERO)) {
                        handler.sendEmptyMessage(NORMAL_ZERO)
                    }
                } else {
                    callback?.call(null)
                }
            } else {
                callback?.call(null)
            }
        }

        /**
         * 获取位置详细信息
         */
        fun getLocationAddress(): LocationInfo? {
            if (lastLocationInfo == null) {
                getLocationOnce(null)
                return null
            }
            if (System.currentTimeMillis() - lastLocationInfo!!.updateTime > LOCATION_DELAY) {
                getLocationOnce(null)
            }
            return lastLocationInfo
        }
    }

    private class LocationMonitor : LocationListener {
        override fun onLocationChanged(location: Location) {
            serviceRunning = true
            emitter?.onNext(location)
        }

        //Android 29 必须要实现这些方法，否则会报错找不到AbstractMethodError onStatusChanged
        @Deprecated("Deprecated in Java", ReplaceWith("Unit"))
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) = Unit

        override fun onProviderDisabled(provider: String) = Unit

        override fun onProviderEnabled(provider: String) = Unit
    }
}